---
title: 读取 Provider
---

import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import CodeBlock from "@theme/CodeBlock";
import counter from "./reading/counter";
import consumerWidget from "./reading/consumer_widget";
import consumerStatefulWidget from "./reading/consumer_stateful_widget";
import consumerHook from "!!raw-loader!/docs/concepts/reading/consumer_hook.dart";
import watch from "./reading/watch";
import watchBuild from "./reading/watch_build";
import listen from "./reading/listen";
import listenBuild from "./reading/listen_build";
import read from "./reading/read";
import readBuild from "./reading/read_build";
import readNotifierBuild from "./reading/read_notifier_build";
import watchNotifierBuild from "./reading/watch_notifier_build";
import provider from "./reading/provider";
import {
  trimSnippet,
  AutoSnippet,
  When,
} from "../../../../../src/components/CodeSnippet";

在阅读本指南之前，请确保先阅读有关[Providers](/docs/concepts/providers)的内容。

在本指南中，我们将了解如何使用provider。

## 获取一个“ref”对象

首先，也是最重要的，在读取provider之前，我们需要获取一个“ref”对象。

这个对象能够让我们与provider交互，不管是来自widget还是其他provider。

### 从provider获取“ref”

所有provider都接收一个“ref”作为参数：

<AutoSnippet language="dart" {...provider}></AutoSnippet>

将此参数传递给provider暴露的值是安全的。

<When codegen={false}>

一个常见的用例是将provider的“ref”传递给`StateNotifier`

</When>

<AutoSnippet language="dart" {...counter}></AutoSnippet>

这样做允许`Counter`类读取provider。

### 从widget获取“ref”

Widget自然没有ref参数。但是 [Riverpod] 提供了多种从widget中获取ref的解决方案。

<When>

#### 扩展ConsumerWidget而不是StatelessWidget

在widget树中获取ref的最常用方法是将 [StatelessWidget] 替换为 [ConsumerWidget] 。

[ConsumerWidget] 在用法上与 [StatelessWidget] 相同，唯一的区别是它在构建方法上有一个额外的参数:“ref”对象。

一个典型的 [ConsumerWidget] 如下所示:

<AutoSnippet language="dart" {...consumerWidget}></AutoSnippet>

#### 扩展ConsumerStatefulWidget+ConsumerState而不是StatefulWidget+State

与 [ConsumerWidget] 类似， [ConsumerStatefulWidget] 和 [ConsumerState]
等价于带有状态的StatefulWidget，区别在于状态中有一个“ref”对象。

这一次，“ref”没有作为构建方法的参数传递，而是作为 [ConsumerState] 对象的属性传递：

<AutoSnippet language="dart" {...consumerStatefulWidget}></AutoSnippet>

</When>

<When hooks>

#### 扩展HookConsumerWidget类而不是HookWidget类

此选项适用于使用 [flutter_hooks] 的用户。由于 [flutter_hooks] 需要扩展 [HookWidget] 才能工作，
使用钩子的widget无法扩展 [ConsumerWidget]。

[hooks_riverpod] package 暴露了一个名为 [HookConsumerWidget] 的widget。
[HookConsumerWidget] 同时充当 [ConsumerWidget] 和 [HookWidget]。
这允许widget既监听provider又使用钩子。

比如说：

<AutoSnippet language="dart" {...consumerWidget}></AutoSnippet>

#### 扩展 StatefulHookConsumerWidget 而不是 HookWidget

此选项适用于 [flutter_hooks] 用户，他们需要使用 [StatefulWidget] 生命周期方法还有钩子。

举个例子：
<AutoSnippet language="dart" {...consumerStatefulWidget}></AutoSnippet>

#### Consumer 和 HookConsumer widgets

获得widget内部的“ref”的最后一种方法是依赖 [Consumer]/[HookConsumer]。

这些类是可用于在build回调中获取“ref”的widget，具有与 [ConsumerWidget]/[HookConsumerWidget] 相同的属性。

因此，这些widget是一种不需要定义类就能获得“ref”的方法。比如：

<CodeBlock>{trimSnippet(consumerHook)}</CodeBlock>

</When>

## 使用ref与provider交互

现在我们有了一个“ref”，我们可以开始使用它了。

“ref”有三种主要用法：

- 获取provider的值并监听更改，这样当该值发生更改时，
  将重新构建订阅该值的widget或provider。  
  这是使用 `ref.watch` 完成的
- 在provider上添加监听器，以执行诸如导航到新页面或每当provider更改时显示模态框等操作。  
  这是使用 `ref.listen` 完成的。
- 在忽略更改的情况下获取provider的值。
  当我们在诸如“on click”之类的事件中需要provider的值时很有用。  
  这是使用 `ref.read` 完成的。

:::note
尽可能使用 `ref.watch` 而不是 `ref.read` 或 `ref.listen` 来实现你的功能。
通过依赖 `ref.watch` ，你的应用变得既具有响应性又具有声明性，这使得项目会更易于维护。
:::

### Using ref.watch to observe a provider
### 使用 ref.watch 观察provider

Ref.watch在widget的`build`方法中或在provider的主体中使用，以使widget/provider监听provider：

例如，一个provider可以使用 `ref.watch` 将多个provider组合成一个新值。

筛选待办清单就是一个例子。我们可以有两个provider：

- `filterTypeProvider`，一个能够暴露当前过滤器类型(不显示，只显示完成的内容等等)的provider。
  
- `todosProvider`，暴露整个待办清单列表的provider。

通过 `ref.watch`，我们可以创建第三个provider，
它结合了这两个provider来创建一个过滤过的待办清单列表：

<AutoSnippet language="dart" {...watch}></AutoSnippet>

有了这段代码，`filteredTodoListProvider` 现在暴露了过滤后的清单列表。

如果筛选器或待办清单列表发生变化，筛选后的列表也会自动更新。
同时，如果过滤器和待办清单列表都没有改变，则不会重新计算那个列表。

类似地，widget可以使用ref.watch显示来自provider的内容，
并在内容发生变化时更新用户界面：

<AutoSnippet language="dart" {...watchBuild}></AutoSnippet>

这个代码段显示了一个widget，它监听了存储`count`的provider。
如果该`count`发生变化，widget将重新构建，UI将更新以显示新的值。

:::caution
像在 [ElevatedButton] 的 `onPressed` 中那样，`watch` 方法不应该被异步调用。
它也不应该在 `initState` 和其他 [State] 的生命周期中使用。

在这种情况下，请考虑使用 `ref.read`。
:::

### 使用ref.listen来响应provider的变化

与 `ref.watch` 类似，也可以使用ref.listen来观察一个provider。

The main difference between them is that, rather than rebuilding the widget/provider
if the listened to provider changes, using `ref.listen` will instead call a custom function.
它们之间的主要区别是，如果监听的provider发生更改，
使用 `ref.listen` 将调用自定义的函数，而不是重新构建widget/provider。

这对于在发生特定变化时执行操作很有用，例如在发生错误时显示snackbar。

`ref.listen` 方法需要两个位置参数，第一个是Provider，第二个是当状态改变时我们想要执行的回调函数。
当调用回调函数时将传递前一个状态的值和新状态的值。

`ref.listen` 方法可以在provider内部使用：

<AutoSnippet language="dart" {...listen}></AutoSnippet>

或者在widget的 `build` 方法中：

<AutoSnippet language="dart" {...listenBuild}></AutoSnippet>

:::caution
像在 [ElevatedButton] 的 `onPressed` 中那样，`listen` 方法不应该被异步调用。
它也不应该在 `initState` 和其他 [State] 的生命周期中使用。
:::

### 使用ref.read获取一个provider的状态

`ref.read` 是一种不监听provider状态的方法。

它通常在用户交互触发的函数中使用。
例如，我们可以使用 `ref.read` 在用户单击按钮时将计数器数值加1：

<AutoSnippet language="dart" {...read}></AutoSnippet>

:::note
你应该尽量避免使用 `ref.read` ，因为它不是响应式的。

它的存在是由于使用 `watch` 或 `listen` 会导致问题。如果可以，使用 `watch`/`listen` 更好，尤其是 `watch`。
:::

#### **不要**在build方法中使用 `ref.read`

你可能会想使用 `ref.read` 来优化widget的性能：

<AutoSnippet language="dart" {...readBuild}></AutoSnippet>

但这样做是非常糟糕的，它可能会导致预料之外的bug。

以这种方式使用 `ref.read` 通常会让人觉得“provider暴露的值永远不会改变，
所以使用 `ref.read` 是安全的”。但问题是，
虽然现在的provider可能确实永远不会更新它的值，但你无法保证以后的值还是一样的。

应用往往会发生很多变更，假设在未来，以前从未改变的一个值将需要改变。
如果你使用 `ref.read`，当该值需要更改时，你必须遍历整个代码库将 `ref.read` 更改为 `ref.watch`，
这很容易出错，而且你很可能会忘记某些情况。

但如果一开始就使用 `ref.watch`，当你重构时遇到的问题就会更少。

**_但是我想要用 `ref.read` 来减少小部件重新构建的次数_**

这个想法很好，但需要注意的是，使用 `ref.watch` 也可以达到完全相同的效果(减少重新构建的次数)。

provider提供了许多获取值的方法，同时也减少了重新构建的次数，你可以使用这些方法。

比如，不应该这样：

<AutoSnippet language="dart" {...readNotifierBuild}></AutoSnippet>

我们应该:

<AutoSnippet language="dart" {...watchNotifierBuild}></AutoSnippet>

这两段代码实现了相同的效果：当计数器增加时，我们的按钮也不会重新构建。

另一方面，第二种方法支持重置计数器。例如，应用的另一部分可以调用：

```dart
ref.refresh(counterProvider);
```

<When codegen={false}>


来重新创建 `Counter` 对象。

如果我们在这里使用 `ref.read`，我们的按钮仍将使用之前的 `StateController` 实例，
但实际上该实例已被丢弃，不应该再使用。
当我们正确使用 `ref.watch` 将重新构建按钮以使用新的 `Counter`实例

</When>

<When codegen>

来重新创建 `Counter` 对象。

如果我们在这里使用 `ref.read`，我们的按钮仍将使用之前的 `Counter` 实例，
但实际上该实例已被丢弃，不应该再使用。
而正确使用 `ref.watch` 将重新构建按钮以使用新的 `Counter`实例。

</When>

## 选择读取的方式

根据你想要监听的provider，你可能有多个可以监听的值。

比如，考虑以下 [StreamProvider]：

```dart
final userProvider = StreamProvider<User>(...);
```

当读取这个 `userProvider` 时，你可以：

- 通过监听 `userProvider` 本身 同步读取当前状态：

  ```dart
  Widget build(BuildContext context, WidgetRef ref) {
    AsyncValue<User> user = ref.watch(userProvider);

    return user.when(
      loading: () => const CircularProgressIndicator(),
      error: (error, stack) => const Text('Oops'),
      data: (user) => Text(user.name),
    );
  }
  ```

- 通过监听 `userProvider.stream` 获取关联的 [Stream]：

  ```dart
  Widget build(BuildContext context, WidgetRef ref) {
    Stream<User> user = ref.watch(userProvider.stream);
  }
  ```

- 通过监听 `userProvider.future`，获得一个解析最新值的 [Future] ：

  ```dart
  Widget build(BuildContext context, WidgetRef ref) {
    Future<User> user = ref.watch(userProvider.future);
  }
  ```

不同的provider可能提供用法。  
要了解更多信息，请阅读[API 参考](https://pub.dev/documentation/riverpod/latest/riverpod/riverpod-library.html)来获取每个provider的文档。

## 使用“select”来过滤重建内容

与读取provider相关的最后一个特性是能够减少widget/provider从 `ref.watch` 重新构建的次数，
或者减少 `ref.listen` 执行函数的频率。

记住，这一点很重要，因为默认情况下，监听provider将监听整个对象状态。
但有时widget/provider可能只关心某些属性的更改，而不是整个对象。

比如说，一个provider可能暴露一个 `User` 对象：

```dart
abstract class User {
  String get name;
  int get age;
}
```

但是widget可能只使用用户名：

```dart
Widget build(BuildContext context, WidgetRef ref) {
  User user = ref.watch(userProvider);
  return Text(user.name);
}
```

如果我们简单地使用 `ref.watch`，这将在用户年龄(`age`)发生变化时重新构建widget。

解决方案是使用 `select` 显式地告诉Riverpod我们只想监听 `User` 的 `name` 属性。

更新后的代码如下:

```dart
Widget build(BuildContext context, WidgetRef ref) {
  String name = ref.watch(userProvider.select((user) => user.name));
  return Text(name);
}
```

通过使用 `select`，我们可以指定一个返回我们所关心的属性的函数。

每当 `User` 发生变化时，Riverpod将调用该函数并比较以前和新的结果。
如果它们是不同的(例如当名称更改时)，Riverpod将重新构建widget。

当然了，如果它们相等(例如当年龄改变时)，Riverpod将不会重建widget。

:::info
也可以 `select` 和 `ref.listen` 结合使用：

```dart
ref.listen<String>(
  userProvider.select((user) => user.name),
  (String? previousName, String newName) {
    print('The user name changed $newName');
  }
);
```

这样做只会在名称更改时调用监听器。
:::

:::tip
你不需要返回对象的属性。任何可以使用==的值都可以工作。举个例子：

```dart
final label = ref.watch(userProvider.select((user) => 'Mr ${user.name}'));
```

:::

[stateprovider]: ../providers/state_provider
[providercontainer]: https://pub.dev/documentation/riverpod/latest/riverpod/ProviderContainer-class.html
[providerlistener]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/ProviderListener-class.html
[providerscope]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/ProviderScope-class.html
[consumer]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/Consumer-class.html
[consumerwidget]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/ConsumerWidget-class.html
[consumerstate]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/ConsumerStatefulWidget-class.html
[consumerstatefulwidget]: https://pub.dev/documentation/flutter_riverpod/latest/flutter_riverpod/ConsumerState-class.html
[useprovider]: https://pub.dev/documentation/hooks_riverpod/latest/hooks_riverpod/ref.watch(.html
[elevatedbutton]: https://api.flutter.dev/flutter/material/ElevatedButton-class.html
[streambuilder]: https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html
[riverpod]: https://github.com/rrousselgit/riverpod
[text]: https://api.flutter.dev/flutter/widgets/Text-class.html
[hooks_riverpod]: https://pub.dev/packages/hooks_riverpod
[future]: https://api.dart.dev/stable/2.13.4/dart-async/Future-class.html
[stream]: https://api.dart.dev/stable/2.13.4/dart-async/Stream-class.html
[hookwidget]: https://pub.dev/documentation/flutter_hooks/latest/flutter_hooks/HookWidget-class.html
[hookconsumerwidget]: https://pub.dev/documentation/hooks_riverpod/latest/hooks_riverpod/HookConsumerWidget-class.html
[hookconsumer]: https://pub.dev/documentation/hooks_riverpod/latest/hooks_riverpod/HookConsumer-class.html
[flutter_riverpod]: https://pub.dev/packages/flutter_riverpod
[flutter_hooks]: https://github.com/rrousselGit/flutter_hooks
[statenotifier]: https://pub.dev/documentation/state_notifier/latest/state_notifier/StateNotifier-class.html
[streamprovider]: ../providers/stream_provider
[statelesswidget]: https://api.flutter.dev/flutter/widgets/StatelessWidget-class.html
[statefulwidget]: https://api.flutter.dev/flutter/widgets/StatefulWidget-class.html
[state]: https://api.flutter.dev/flutter/widgets/State-class.html
